package com.alibaba.markovdemo.engine;


import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.markovdemo.BO.*;

import com.alibaba.markovdemo.engine.AI.FMM.GenerateDictionary;
import com.alibaba.markovdemo.engine.AI.FMM.Segmentation;
import com.alibaba.markovdemo.engine.util.Toolkit;
import com.alibaba.markovdemo.entity.GotFeaturesPool;
import com.alibaba.markovdemo.entity.GotTestCase;
import com.alibaba.markovdemo.service.GotFeaturesPoolService;
import com.alibaba.markovdemo.service.TestcaseService;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;


@Service
public class CaseFactoryProcessor {

    private static final Logger Logger = LoggerFactory.getLogger(CaseFactoryProcessor.class);

    @Autowired
    private GotFeaturesPoolService gotFeaturesPoolService;
    @Autowired
    private TestcaseService testcaseService;
    Gson gson = new Gson();
    Segmentation seg;


    /**
     * 函数功能:处理FMM处理后的特征
     * 过滤策略:单个字符不算特征,具体的词才算业务特征
     * @param input
     * @return
     */
    public List<String> getFMMFeatures(String input){

        List<String> featureList = new ArrayList<>();
        String str = seg.Fmm(input);
        String[] list = str.split("/");

        for (String split : list){

            if (split.length()>1){
                featureList.add(split);
            }
        }
        return featureList;

    }

    /**
     * 函数功能:处理query和数据源的特征
     * 过滤策略:数据源名 ,query用户可根据业务需求自行抽取,本次暂不实现
     * @param input
     * @return
     */
    public List<String> getQueryAndDataFeatures(String input){

        List<String> featureList = new ArrayList<>();

        try{

            JSONObject obj =  JSONObject.parseObject(input);
            List<JSONObject> list = (List<JSONObject>) obj.get("prepareData");

            for (JSONObject jObj : list){

                if (jObj.size()>0){
                    for (String dsName : jObj.keySet()){
                        List<JSONObject> dsList = (List<JSONObject>) jObj.get(dsName);
                        for (JSONObject dsObj : dsList){
                            String name = dsObj.getString("dsName");
                            if(!featureList.contains(name)){
                                featureList.add(name);
                            }
                        }

                    }
                }
            }

        }catch (Exception e){
            //格式异常,skip..
        }


        return featureList;

    }


    /**
     * 特征抽取策略:
     * 1.用例详细描述采用FMM分词.
     * 2.分组单独作为特征.
     * 3.数据源名为特征
     * 其他:其他维度特征用户可根据业务自行补充
     * @param gotTestCase
     * @return
     */
    public List<String>  extractFeatures(GotTestCase gotTestCase){

        //用例特征
        List<String> featureList = new ArrayList<>();
        //用例名进行FMM分词
        List<String>  descriptionFeatures = getFMMFeatures(gotTestCase.getDescription());
        //用例描述FMM分词
        List<String> longDescriptionFeatures = getFMMFeatures(gotTestCase.getLongDescription());
        //分组特征
        String caseGroupFeatures = gotTestCase.getCaseGroup();
        //数据源和query特征
        List<String> queryAndDataFeatures = getQueryAndDataFeatures(gotTestCase.getContent());

        if (caseGroupFeatures!=null && !caseGroupFeatures.equals("")){
            featureList.add(caseGroupFeatures);
        }
        featureList.addAll(descriptionFeatures);
        featureList.addAll(longDescriptionFeatures);
        featureList.addAll(queryAndDataFeatures);

        return featureList;
    }

    /**
     * 函数功能:用例特征并入特征池
     * @param featurePool
     * @param featuresList
     */
    public void putInFeatures(Map<String,Integer>  featurePool,List<String> featuresList){

        if (featuresList.size()>0){
            for (String features :  featuresList){

                if (!featurePool.containsKey(features)){
                    featurePool.put(features,1);
                }
                else{
                    featurePool.put(features,featurePool.get(features)+1);
                }
            }
        }

    }


    /**
     * 函数功能:加载字典,分词使用
     * @throws IOException
     */
    public void loadDict() throws IOException {
        //加载词库
        String filename = System.getProperty("user.dir") + "/src/main/java/com/alibaba/markovdemo/engine/AI/FMM/doc/dict.txt";
        HashMap hm = new HashMap();
        HashMap len = new HashMap();
        GenerateDictionary genDic = new GenerateDictionary();
        genDic.GenHashDic(filename, hm, len);
        seg = new Segmentation(hm, len);
    }



    /**
     * 函数功能:特征抽取
     * 1.使用FMM算法对用例描述进行分词(用户可根据需要自行采用其他分词算法,本篇采用了经典FMM分词算法)
     * 2.对数据源/发送query进行关键字段抽取
     * 3.用例维度/场景维度的特征入库
     * @param scenarioId
     */
    public void callFeaturesExtract(Long scenarioId) throws IOException {


        Map<String,Integer> featurePool = new HashMap<>();
        GotFeaturesPool  gotFeaturesPool = new GotFeaturesPool();

        //获取所有的用例的业务数据
        List<GotTestCase> gotTestCaseList = testcaseService.findByScenarioId(scenarioId);
        //加载词库
        loadDict();

        //遍历用例库
        for (GotTestCase  gotTestCase : gotTestCaseList){
            try{
                //抽取用例特征集
                List<String> featuresList = extractFeatures(gotTestCase);
                //特征池合并
                putInFeatures(featurePool,featuresList);
                //单个用例特征入库
                gotTestCase.setFeatures(gson.toJson(featurePool));
                testcaseService.update(gotTestCase);
                System.out.print("用例id:"+ gotTestCase.getId() +"特征抽取完成!\n");
            }
            catch (Exception e){
                System.out.print("用例id:"+ gotTestCase.getId() +"特征抽取过程出错,请注意!\n");
                e.printStackTrace();
            }

        }
        //特征池入库
        gotFeaturesPool.setId(scenarioId);
        gotFeaturesPool.setScenarioId(scenarioId);
        gotFeaturesPool.setFeatures(gson.toJson(featurePool));
        gotFeaturesPoolService.insert(gotFeaturesPool);
        System.out.print("特征池入库完成!\n");
    }


    /**
     * 函数功能:异步执行全量用例特征抽取
     * @param scenarioId
     */
    public void featuresExtract(Long scenarioId) throws IOException {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Logger.info("提交特征抽取任务");
                    callFeaturesExtract( scenarioId);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

    }

    // 升序比较器
    Comparator<Map.Entry<String, Double>> valueComparator = new Comparator<Map.Entry<String,Double>>() {
        @Override
        public int compare(Map.Entry<String, Double> o1,
                           Map.Entry<String, Double> o2) {
            // TODO Auto-generated method stub
            return (int) (o1.getValue()-o2.getValue());
        }
    };

    // 升序比较器
    Comparator<Map.Entry<GotTestCase, Double>> valueCaseComparator = new Comparator<Map.Entry<GotTestCase,Double>>() {
        @Override
        public int compare(Map.Entry<GotTestCase, Double> o1,
                           Map.Entry<GotTestCase, Double> o2) {
            // TODO Auto-generated method stub
            return (int) (o1.getValue()-o2.getValue());
        }
    };


    /**
     * 函数功能:获取特征列表
     * 策略:按照特征的出现频率降序返回
     * @param scenarioId
     * @return
     */
    public List<String> getFeaturesList(Long scenarioId){

        List<String> featureList = new ArrayList<>();

        try{
            List<String> sortList = new ArrayList<>();
            GotFeaturesPool gotFeaturesPool = gotFeaturesPoolService.findByScenarioId(scenarioId);
            // map转换成list进行排序
            Map<String, Double> map = new HashMap<>();
            map = gson.fromJson(gotFeaturesPool.getFeatures(), map.getClass());

            List<Map.Entry<String, Double>> list = new ArrayList<>(map.entrySet());
            // 排序
            Collections.sort(list,valueComparator);
            for (Map.Entry<String, Double> entry : list) {
                sortList.add(entry.getKey());
            }
            return sortList;
        }
        catch (Exception e){
            e.printStackTrace();
        }

        return featureList;

    }


    /**
     * 函数功能:匹配特征命中数目
     * @param feature
     * @param featuresList
     * @return
     */
    public Integer getMatchFeatureNum(String feature, List<String> featuresList){

        JSONObject jList= JSONObject.parseObject(feature);
        List<String> list = new ArrayList<>();
        for (String str :jList.keySet()){
            list.add(str);
        }
        //交集处理
        list.retainAll(featuresList);

        return list.size();

    }

    /**
     * 函数功能:特征匹配
     * 返回相似度TopN用例集,此处采用简单的特征值命中策略,即交集特征越多,相似度越高
     * @param featureInfo
     * @return
     */
    public JSONObject FeaturesMatch(FeatureInfo featureInfo) throws IOException {

        Date start = new Date();
        JSONObject res = new JSONObject();
        Map<GotTestCase,Double> map = new HashMap<>();
        List<TestCaseInput> sortList = new ArrayList<>();

        //对业务描述进行分词,并合并入匹配特征列表
        if(featureInfo.getCaseDesc()!=null){
            loadDict();
            List<String>  descriptionFeatures = getFMMFeatures(featureInfo.getCaseDesc());
            featureInfo.getFeatureList().addAll(descriptionFeatures);
        }

        List<GotTestCase> gotTestCaseList = testcaseService.findByScenarioId(featureInfo.getScenarioId());
        //遍历用例库
        for (GotTestCase  gotTestCase : gotTestCaseList){
            try{
                //命中特征的数目
                Integer num = getMatchFeatureNum(gotTestCase.getFeatures(),featureInfo.getFeatureList());
                map.put(gotTestCase, Double.valueOf(num));
                System.out.print("用例id:"+ gotTestCase.getId() +"特征匹配完成!\n");
            }
            catch (Exception e){
                System.out.print("用例id:"+ gotTestCase.getId() +"特征匹配过程出错,请注意!\n");
                map.put(gotTestCase, (double) 0);
            }
        }

        List<Map.Entry<GotTestCase, Double>> list = new ArrayList<>(map.entrySet());
        // 降序排序
        Collections.sort(list,valueCaseComparator);
        int cnt = 0;
        for (Map.Entry<GotTestCase, Double> entry : list) {
            cnt++;
            if (cnt<=featureInfo.getTopN()){
                sortList.add(TestcaseService.FormatTestCaseInput(entry.getKey()));
            }
        }
        Date end = new Date();

        res.put("caseList",sortList);
        res.put("caseCount",sortList.size());
        res.put("interval",end.getTime() - start.getTime());
        res.put("recommenderName","FMM算法");

        return res;
    }

    //====以下是用例膨胀===

    /**
     * 函数功能:获取可进行参数组合的Feature集
     * 注意:demo中针对请求串中的tesla参数进行用例膨胀,实际使用中,用户可以根据自己的业务自行改造.
     * @param testCaseInput
     * @return
     */
    public JSONArray getExpandFeatures( TestCaseInput testCaseInput){

        //对于符合query格式可进行用例膨胀
        try{
            //query
            String query = testCaseInput.getCaseRunStage().get(0).getData().get(0).getInput();
            JSONObject inputObj =  JSONObject.parseObject(query);
            JSONObject paramsObj = (JSONObject) inputObj.get("param_manager");
            JSONObject teslaObj = (JSONObject) paramsObj.get("expand_param");
            return (JSONArray) teslaObj.get("key_value_list");
        }
        catch (Exception e){
            return new JSONArray();
        }

    }


    /**
     * 函数功能:获取膨胀组合
     * 此处的膨胀组合是demo使用,真实场景用户可进行自定义,或者是自行训练出来.
     * @return
     */
    public JSONObject initFeatureCombination(){

        Map<String,String> featureCombination = new HashMap<>();
        //bool类型
        featureCombination.put("布尔类型","<true>,<false>");
        //Int32型组合
        featureCombination.put("Int32型组合","<0>,<-2147483648>,<2147483647>,<100>,<-100>");
        //Int型组合
        featureCombination.put("Int16型组合","<0>,<-32768>,<32767>,<100>,<-100>");
        //Long型组合,边界组合
        featureCombination.put("Long型组合","<0>,<-18446744073709551616>,<18446744073709551615>,<100>,<-100>");
        //字符串组合,乱码
        featureCombination.put("字符串组合","<nomalStr>,<?/@#%^^&&>");

        //以下用户可自行引入关于具体业务特征抽取算法
        featureCombination.put("线上抽取特征组合","<1/0/1>,<1/0/2>,<2/0/1>,<3/0/1>");
        featureCombination.put("FMM算法抽取特征组合","<猜你喜欢>,<直通车>,<定向>,<pid>");


        List<JSONObject> list = new ArrayList<>();
        for (String key : featureCombination.keySet()){

            JSONObject kv = new JSONObject();
            kv.put("key",key);
            kv.put("value",featureCombination.get(key));
            list.add(kv);
        }
        JSONObject res = new JSONObject();
        res.put("featureInitMap",featureCombination);
        res.put("featureInitList",list);

        return res;
    }

    /**
     * 函数功能:非递归计算所有膨胀组合
     * @param inputList 所有数组的列表
     * */
    public List<List<String>> calculateCombination(List<List<String>> inputList) {
        List<Integer> combination = new ArrayList<>();
        List<List<String>> outputList = new ArrayList<>();
        List<String> oneOutput ;
        int n=inputList.size();
        for (int i = 0; i < n; i++) {
            combination.add(0);
        }
        int i=0;
        boolean isContinue=false;
        do{
            oneOutput = new ArrayList<>();
            //添加一次循环生成的膨胀组合
            for (int j = 0; j < n; j++) {
//                System.out.print(inputList.get(j).get(combination.get(j))+", ");
                oneOutput.add(inputList.get(j).get(combination.get(j)));
            }
            outputList.add(oneOutput);

            i++;
            combination.set(n-1, i);
            for (int j = n-1; j >= 0; j--) {
                if (combination.get(j)>=inputList.get(j).size()) {
                    combination.set(j, 0);
                    i=0;
                    if (j-1>=0) {
                        combination.set(j-1, combination.get(j-1)+1);
                    }
                }
            }
            isContinue=false;
            for (Integer integer : combination) {
                if (integer != 0) {
                    isContinue=true;
                }
            }
        }while (isContinue);

        return outputList;
    }

    /**
     * 函数功能:将需要膨胀的kv集合进行切分
     * @param expandkvList
     * @return
     */
    List<List<String>> getExpandKvGroupList(List<Expandkv> expandkvList){

        //首先扫描,expandValue是分隔,如果为null,""
        List<List<String>> teslaCalList = new ArrayList<>();

        //转化为参数组
        for(Expandkv expandkv : expandkvList){
            String expandValue = expandkv.getExpandValue();
            String key = expandkv.getKey();
            List<String> teslaCal = new ArrayList<>();

            try{
                if(expandValue.equals("") || expandValue == null){
                    continue;
                }
                String[] valList = expandValue.split(">,<");
                for(String val : valList){
                    val = val.replace("<","");
                    val = val.replace(">","");
                    String kvStr = key + ":" + val;
                    teslaCal.add(kvStr);
                }
                teslaCalList.add(teslaCal);
            }catch (Exception e){
                //不符合格式的则忽略.
            }

        }

        return calculateCombination(teslaCalList);

    }
    /**
     * 函数功能:获取膨胀用例列表
     * 膨胀策略:demo中采用参数进行叉乘组合的方式进行膨胀,更多复杂的策略用户可根据业务进行添加
     * @return
     */
    public List<TestCaseInput> getExpandCases(ExpandInfo expandInfo){

        List<TestCaseInput> caseList = new ArrayList<>();
        TestCaseInput testCase = expandInfo.getTestCase();
        List<Expandkv> expandKvList = expandInfo.getExpandKvList();
        List<List<String>> expandKvGroupList = getExpandKvGroupList(expandKvList);
        //遍历膨胀参数列表
        for (List<String> oneExpandKvGroup : expandKvGroupList){
            TestCaseInput expandcase = (TestCaseInput) testCase.clone();
            //修改desc
            String caseDesc = expandcase.getDescription() + ".[用例膨胀组合]:" + Toolkit.implode("|",oneExpandKvGroup);
            expandcase.setDescription(caseDesc);
            //修改input
            String erpc = expandcase.getCaseRunStage().get(0).getData().get(0).getInput();
            JSONObject erpcObj = (JSONObject) JSONObject.parse(erpc);
            JSONObject paramsObj = (JSONObject) erpcObj.get("param_manager");
            JSONObject teslaObj = (JSONObject) paramsObj.get("expand_param");

            JSONArray kvObjListclone = new JSONArray();
            for(String onekv : oneExpandKvGroup){
                String[] kv = onekv.split(":");
                if(kv.length == 2){
                    JSONObject kvObj = new JSONObject();
                    kvObj.put("key",kv[0]);
                    kvObj.put("value",kv[1]);
                    kvObjListclone.add(kvObj);
                }
            }
            teslaObj.put("key_value_list",kvObjListclone);
            expandcase.getCaseRunStage().get(0).getData().get(0).setInput(erpcObj.toString());
            caseList.add(expandcase);
        }

        return caseList;
    }


    /**
     * 函数功能:在用户选择了批量膨胀的用例集后,可进行批量保存用例
     * @param data
     * @return
     */
    public AjaxResult saveCaseList( MultiCase data){
        try {

            int i = 0;

            //遍历待保存的用例集
            for(TestCaseInput testcase :data.getCaseList()){

                testcase.setId(null);

                //如果是用例膨胀,直接保存入库
                if(data.getMultiInfo().getType().equals("expand")){
                    testcaseService.saveTestCase(testcase);
                }
                //如果是推荐用例,可做改造后在保存
                if(data.getMultiInfo().getType().equals("recommend")){
                    //修改desc
                    String caseDesc = data.getMultiInfo().getCaseDesc();
                    i++;
                    caseDesc =  caseDesc + "[系统推荐" + i + "]";
                    testcase.setDescription(caseDesc);
                    testcase.setTag("系统推荐");

                    try{
                        //此处是根据可膨胀的字段进行mock,使用方可自行定义
                        String erpc = testcase.getCaseRunStage().get(0).getData().get(0).getInput();
                        JSONObject erpcObj = (JSONObject) JSONObject.parse(erpc);
                        JSONObject paramsObj = (JSONObject) erpcObj.get("param_manager");
                        JSONObject teslaObj = (JSONObject) paramsObj.get("expand_param");
                        JSONArray kvObjList = (JSONArray) teslaObj.get("key_value_list");
                        kvObjList.clear();

                        for(Expandkv onekv : data.getMultiInfo().getExpandkvist()){
                            JSONObject kvObj = new JSONObject();
                            if(onekv.getKey()!= null && onekv.getValue() != null){
                                kvObj.put("key",onekv.getKey());
                                kvObj.put("value",onekv.getValue());
                                kvObjList.add(kvObj);
                            }

                        }
                        testcase.getCaseRunStage().get(0).getData().get(0).setInput(erpcObj.toString());
                        testcaseService.saveTestCase(testcase);
                    }
                    catch (Exception e) {
                        //如果请求不符合规范,则不再改造请求串,直接入库保存.
                        testcaseService.saveTestCase(testcase);
                    }
                }

            }
            return AjaxResult.succResult("save ok");
        } catch (Exception e) {
            return AjaxResult.errorResult(e.getMessage());
        }

    }

}
